package mse.tx.arch.pipe.filters;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Queue;

import mse.tx.arch.pipe.framework.DataModel;
import mse.tx.arch.pipe.framework.FilterFramework;
import mse.tx.arch.pipe.framework.IConfigurable;

public class PressureScreenerFilter extends FilterFramework implements IConfigurable {

	private double upperBound = 80;
	private double lowerBound = 50;

	public PressureScreenerFilter() {
		addInputPortByName("input");
		addOutputPortByName("output");
		addOutputPortByName("reject");
	}

	public PressureScreenerFilter(double upperBound, double lowerBound) {
		addInputPortByName("input");
		addOutputPortByName("output");
		addOutputPortByName("reject");
		this.lowerBound = lowerBound;
		this.upperBound = upperBound;
	}

	public void run() {
		System.out
				.print("\n" + this.getName() + "::Pressure Filter Reading \n");

		double lastValidPressure = Double.MIN_VALUE;
		double nextValidPressure = Double.MIN_VALUE;

		double pressure = 0;

		int id = 0;
		long measurement = 0;

		DataModel dataRead;
		Queue<List<DataModel>> dataBuffer = new ArrayDeque<List<DataModel>>();
		List<DataModel> aFrame = new ArrayList<DataModel>();

		try {
			while (true) {
				id = readID();
				measurement = readMeasurement();
				
				if(id == 0 && !aFrame.isEmpty()){
					
					pressure = findPressure(aFrame);
					if(pressure != Double.MIN_VALUE && (pressure > upperBound || pressure < lowerBound)){
						
						dataBuffer.add(new ArrayList<DataModel>(aFrame));
						
					}else{
						if(!dataBuffer.isEmpty()){
							nextValidPressure = pressure;
							while (!dataBuffer.isEmpty()) {
								List<DataModel> prevFrame = dataBuffer.poll();
								
								for(DataModel model : prevFrame){
									
									writeID("reject", model.getId());
									writeMeasurement("reject", model.getMeasurement());
									
									double extrapolatedValue = nextValidPressure;
									
									if(model.getId() == DataModel.PRESSURE_ID){
										if(lastValidPressure != Double.MIN_VALUE)
											extrapolatedValue = (lastValidPressure + nextValidPressure) / 2;
										model.setMeasurement(Double.doubleToLongBits(extrapolatedValue));
										writeID("output", DataModel.EXTAPLOTATED);
										writeMeasurement("output", 0l);
									}
									writeID("output", model.getId());
									writeMeasurement("output", model.getMeasurement());
								}
							}
						}
						for(DataModel model : aFrame){
							writeID("output", model.getId());
							writeMeasurement("output", model.getMeasurement());
						}
						lastValidPressure = pressure;
					}
					aFrame.clear();
				}
				
				dataRead = new DataModel();
				dataRead.setId(id);
				dataRead.setMeasurement(measurement);
				aFrame.add(dataRead);	
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (EndOfStreamException e) {
			//e.printStackTrace();
		} finally {
			try{
				if (!aFrame.isEmpty()) {

					pressure = findPressure(aFrame);
					if (pressure != Double.MIN_VALUE
							&& (pressure > upperBound || pressure < lowerBound)) {

						dataBuffer.add(new ArrayList<DataModel>(aFrame));

					}
				}

				if (!dataBuffer.isEmpty()) {
					nextValidPressure = pressure;
					while (!dataBuffer.isEmpty()) {
						List<DataModel> prevFrame = dataBuffer.poll();

						for (DataModel model : prevFrame) {

							writeID("reject", model.getId());
							writeMeasurement("reject",
									model.getMeasurement());

							if (model.getId() == DataModel.PRESSURE_ID) {
								model.setMeasurement(Double
										.doubleToLongBits(lastValidPressure));
								writeID("output", DataModel.EXTAPLOTATED);
								writeMeasurement("output", 0l);
							}
							writeID("output", model.getId());
							writeMeasurement("output",
									model.getMeasurement());
						}
					}
				}
				for (DataModel model : aFrame) {
					writeID("output", model.getId());
					writeMeasurement("output", model.getMeasurement());
				}
				lastValidPressure = pressure;

				aFrame.clear();

			
			}catch(Exception e){
				e.printStackTrace();
			}
			closePorts();
		}
	}

	private double findPressure(List<DataModel> aFrame){
		for(DataModel model : aFrame){
			if(model.getId() == DataModel.PRESSURE_ID)
				return Double.longBitsToDouble(model.getMeasurement());
		}
		return Double.MIN_VALUE;
	}
	
	private int readID() throws IOException, EndOfStreamException {
		int IdLength = 4; // This is the length of IDs in the byte stream

		int id = 0;

		byte databyte;

		for (int i = 0; i < IdLength; i++) {
			databyte = readFilterInputPort("input");

			id = id | (databyte & 0xFF);

			if (i != IdLength - 1) {
				id = id << 8;
			}
		}
		return id;
	}

	private long readMeasurement() throws IOException, EndOfStreamException {
		int MeasurementLength = 8;

		long measurement = 0;

		byte databyte;

		for (int i = 0; i < MeasurementLength; i++) {
			databyte = readFilterInputPort("input");
			measurement = measurement | (databyte & 0xFF);
			if (i != MeasurementLength - 1) {
				measurement = measurement << 8;
			}
		}

		return measurement;
	}

	private void writeID(String outputPortName, int id) throws IOException {
		int IdLength = 4;

		byte databyte;

		for (int i = IdLength; i > 0; --i) {

			databyte = (byte) (id >> ((i - 1) * 8) & 0xFF);

			writeFilterOutputPort(outputPortName, databyte);
		}
	}

	private void writeMeasurement(String outputPortName, long measurement)
			throws IOException {
		int MeasurementLength = 8;

		byte databyte;

		for (int i = MeasurementLength; i > 0; --i) {

			databyte = (byte) (measurement >> ((i - 1) * 8) & 0xFF);

			writeFilterOutputPort(outputPortName, databyte);
		}
	}

	@Override
	public void setConfiguration(Properties props) {
		if (props != null) {
			upperBound = Double.parseDouble(props.getProperty("upperBound"));
			lowerBound = Double.parseDouble(props.getProperty("lowerBound"));
		}
	}
}
